package wiiusej;

import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Vector;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import wiiusej.apptest.statistics.performance.ClassTrackName;
import wiiusej.apptest.statistics.performance.PerformanceUnit;
import wiiusej.apptest.statistics.performance.Track;
import wiiusej.apptest.statistics.performance.TrackType;
import wiiusej.wiiusejevents.GenericEvent;
import wiiusej.wiiusejevents.utils.EventsGatherer;
import wiiusej.wiiusejevents.wiiuseapievents.StatusEvent;
import wiiusej.wiiusejevents.wiiuseapievents.WiiUseApiEvent;
import wiiusej.wiiusejevents.wiiuseapievents.WiimoteEvent;

public class MessageDeliver implements Runnable{

	//Logger 
	private static Logger fileLogger;
	
	private static final int NTHREADS = 200;
	
	private static final ExecutorService executor = 
		Executors.newFixedThreadPool(NTHREADS);
	
	//class name
	private final ClassTrackName className = ClassTrackName.POSTMAN;
	
	//API Manager
	private static WiiUseApiManager wiimoteManager;
	
	//COM QUEUES 
	private BlockingQueue<HashMap<Integer, BlockingQueue<GenericEvent>>> fromConnection;
	private BlockingQueue<WiiUseApiEvent> toConnection;
	private BlockingQueue<WiiUseApiEvent> toNetListener;
	private BlockingQueue<Track> toPerformanceController;
	
	//COM CHANNELS
	private static HashMap<Integer, BlockingQueue<GenericEvent>> wiimoteComChannels;
	
	private int connected = 0;
	private AtomicBoolean running = new AtomicBoolean(false);
	
	
	private int statusEventCounter = 0;
	private long genericPacketCounter = 1;
	private long statusPacketCounter = 0;
	
	
	
	public MessageDeliver(BlockingQueue<HashMap<Integer, BlockingQueue<GenericEvent>>> connectionToAPI, 
			BlockingQueue<WiiUseApiEvent> postmanToConnection, WiiUseApiManager controller,
			BlockingQueue<WiiUseApiEvent> toNetListener, BlockingQueue<Track> fromSystem){
		
		this.fromConnection = connectionToAPI;
		this.toConnection = postmanToConnection;
		this.toNetListener = toNetListener;
		this.toPerformanceController = fromSystem;
		
		this.wiimoteManager = controller;
		
		//Logger init
		PropertyConfigurator.configure("log4j.properties");
		fileLogger = Logger.getLogger("messageDeliver");
		
		fileLogger.info("Init Message Deliver");
		
		
	}
	
	
	
	@Override
	public void run() {
		
		
		SimpleDateFormat tsFormat = new SimpleDateFormat("mm:ss:SS");
		
		//listen the blocking queue from ConnectionManager
		try {
			wiimoteComChannels = fromConnection.take();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		fileLogger.debug("Received Message from ConnectionManger - Wiimotes: " 
				+ wiimoteComChannels.size() + " Time: " + tsFormat.format(System.currentTimeMillis()));	
		
		this.connected = wiimoteComChannels.size();
		
		fileLogger.debug("Found Connected Channels: " + wiimoteComChannels + 
				"Connected: " + this.connected);
		
		running.set(true);		
		
		EventsGatherer gather = new EventsGatherer(connected);	
			
		//Discard Events which have arrived before the wake-up process
		gather = wiimoteManager.specialPoll(connected);
		gather.clearEvents();
		
		fileLogger.debug("");
		fileLogger.debug("*******************************");
		fileLogger.debug("EVENTS GATHERED BEFORE INIT APP: " + gather.getEvents().size());
		fileLogger.debug("*******************************");
		fileLogger.debug("");			
		
		while (running.get() && !wiimoteComChannels.isEmpty()) {
						
			//Check if there were changes in the wiimote connection
			HashMap<Integer, Wiimote> uploadedWiimotes;
			Vector<WiiUseApiEvent> events = gather.getEvents();
			
			
			while(events.isEmpty()){
				//EVENT POLLING
				gather = wiimoteManager.specialPoll(connected);
				events = gather.getEvents();
				fileLogger.info("Gathering Events...");				
			}
			
			long start = System.currentTimeMillis();
			Timestamp time = new Timestamp(start);
			
			fileLogger.debug("EventsGathered: " + events.size() + " Time: " + tsFormat.format(time));
			
			for(WiiUseApiEvent e : events){				
				if(e.getEventType() == WiiUseApiEvent.GENERIC_EVENT){
					tagPackets(e, time, tsFormat);
					sendToPerformanceController(e);
				}								
			}
			
			assignToAssistant(events);
			
			
			
			/*			
			//deal with events gathered in Wiiuse API
			for (WiiUseApiEvent evt : events) {
				
				statusPacketCounter++;
				
				fileLogger.info("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
				fileLogger.info("Event Received: " + evt.getEventType() 
						+ " From: " + evt.getWiimoteId() 
						+ " at: " + tsFormat.format(System.currentTimeMillis()) + 
						" #: " + statusPacketCounter);
				fileLogger.info("");						
				
				
				if (evt.getWiimoteId() != -1) {// event filled					
					
					fileLogger.info("Classifying the Event" + " Type: " + evt.getEventType());
					
					switch(evt.getEventType()){
					
					case WiiUseApiEvent.DISCONNECTION_EVENT:
						
						fileLogger.info("");
						fileLogger.info("DISCONNECTION EVENT RECEIVED");
						fileLogger.info("");
						
						try {
							toConnection.put(evt);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						
						break;
						
					case WiiUseApiEvent.STATUS_EVENT:
						
						fileLogger.info("Status Event Received - Sudando!");
						statusEventCounter++;										
						evt.setRefNumber(statusEventCounter);
						StatusEvent status = (StatusEvent) evt;
						
						
						//fileLogger.info(status);
						//fileLogger.info("*********** Sending Status info to NetListener: " + statusEventCounter);
						//fileLogger.info("");
						//notifyNetListener(evt);
						break;
					
					default: 
						fileLogger.info("Notifying the listeners");
						notifyWiiUseApiListener(evt);
						break;
					}					
					
				} else {
					fileLogger.error("NON-VALID WIIMOTE ID");
				}
			}*/
			
			fileLogger.info("Clearing Gather object");
			gather.clearEvents();
			fileLogger.debug("");
			
			
		}		
		
		
	}

	
	private void tagPackets(WiiUseApiEvent e, Timestamp time, SimpleDateFormat tsFormat){
		e.setRefNumber(genericPacketCounter);
		fileLogger.debug("Packet#: " + e.getRefNumber() + " TimeIN: " + tsFormat.format(time));
		genericPacketCounter++;		
	}
	
	
	
	private void assignToAssistant(Vector<WiiUseApiEvent> events) {
		Runnable assistant = new DeliveryAssistant(wiimoteComChannels, toNetListener, 
				toPerformanceController, events, genericPacketCounter, fileLogger);
		executor.execute(assistant);	
		//genericPacketCounter += events.size();	
		
		//fileLogger.debug("*** RefNumber Uploaded: " + genericPacketCounter);
		
	}

	/*
	private void assignToAssistant2(Vector<WiiUseApiEvent> events) {
		Runnable assistant = new DeliveryAssistant(wiimoteComChannels, toNetListener, 
				toPerformanceController, events, genericPacketCounter, fileLogger);
		executor.execute(assistant);	
		genericPacketCounter += events.size();		
		
	}*/


	private void notifyNetListener(WiiUseApiEvent evt) {
		
		try {				
			toNetListener.put(evt);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}	
		
	}



	/**
	 * Notify WiiUseApiListeners that an event occured.
	 * 
	 * @param evt
	 *            GenericEvent occured
	 */
	private void notifyWiiUseApiListener(WiiUseApiEvent evt) {
		
		String myName = Integer.toString(evt.getWiimoteId());
		
		if (evt.getEventType() == WiiUseApiEvent.GENERIC_EVENT) {
			
			HashMap<String, Timestamp> timeStamp;		
			Timestamp ts = new Timestamp (System.currentTimeMillis());
			timeStamp = ((WiimoteEvent) evt).getMotionSensingEvent().getTimeStamp();
			
			timeStamp = new HashMap<String, Timestamp>();
			timeStamp.put(myName,ts);
			
			((WiimoteEvent) evt).getMotionSensingEvent().setTimeStamp(timeStamp);
			((WiimoteEvent) evt).getMotionSensingEvent().setTime(ts);
			
			((WiimoteEvent) evt).setTime(ts);
			
			//set refNumber		
			genericPacketCounter++;
			evt.setRefNumber(genericPacketCounter);
			fileLogger.info("New reference Number: " + genericPacketCounter + 
					" PacketNumber: " + evt.getRefNumber());		
			
			
			sendToPerformanceController(evt);
			
		}
		
		
		
		//Select the channel regarding the wiimoteID
		BlockingQueue<GenericEvent> q = wiimoteComChannels.get(evt.getWiimoteId());
		
		if( q !=  null){
			try {				
				q.put(evt);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}			
			
		}else{
			fileLogger.info("ERROR finding comunucation channel. Wiimote: " + evt.getWiimoteId() + 
					" does NOT exists");
		}		
		
	}



	private void sendToPerformanceController(WiiUseApiEvent evt) {
		Timestamp time = new Timestamp(System.currentTimeMillis());
		Track track = new Track(className, time, evt.getRefNumber(), evt.getWiimoteId(), 
				TrackType.ACCELERATION);
		
		try {
			toPerformanceController.put(track);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
	}
	
	
	
	
}
